/*=========================================================================
| KCardDAV
|--------------------------------------------------------------------------
| (c) 2010  Kumaran Santhanam
|
| This project is released under the GNU General Public License.
| Please see the file COPYING for more details.
|--------------------------------------------------------------------------
| Download job class.
 ========================================================================*/

/*=========================================================================
| INCLUDES
 ========================================================================*/

#include "download.h"
#include "resource.h"

#include <kabc/vcardconverter.h>

#include <kdebug.h>
#include <klocalizedstring.h>
#include <string>
#include <vector>
#include <string.h>

#include <QtCore/QByteArray>

#include <neon/ne_alloc.h>
#include <neon/ne_xml.h>

#define log(s)      kDebug() << s;

/*=========================================================================
| NAMESPACE
 ========================================================================*/

using namespace KABC::carddav;

/*=========================================================================
| Impl
 ========================================================================*/

struct DownloadJob::Impl {
    enum {
        GET_OBJECTS_LIST = 0,
        GET_OBJECTS_DATA,
        FINISHED
    };

    enum {
        XML_ABORT = 0,
        XML_CONTINUE,
        XML_RESPONSE,
        XML_HREF,
        XML_RESOURCE_TYPE,
        XML_COLLECTION,
        XML_ADDRESS_DATA,
        XML_ADDRESS_MULTISTATUS
    };

    DownloadJob* owner;
    int state;
    std::vector<std::string> hrefs;
    ne_xml_parser* parser;
    std::string cur_href;
    QByteArray vcards;

    Impl(DownloadJob* anOwner);
    ~Impl();

    void destroyParser();
    ne_request* createListRequest(ne_session* session);
    ne_request* createDataRequest(ne_session* session);
    void clearResource(ResourceCardDav* customer);

    static int startelm_cb(void *userdata, int parent, const char *nspace, const char *name, const char **atts);
    static int cdata_cb(void *userdata, int state, const char *cdata, size_t len);
    static int endelm_cb(void *userdata, int state, const char *nspace, const char *name);
};

DownloadJob::Impl::Impl(DownloadJob* anOwner)
    : owner(anOwner)
    , state(GET_OBJECTS_LIST)
    , parser(0)
{
}

DownloadJob::Impl::~Impl() {
    destroyParser();
}

void DownloadJob::Impl::destroyParser() {
    if (parser) {
        ne_xml_destroy(parser);
    }
}

ne_request* DownloadJob::Impl::createListRequest(ne_session* session) {
    log("building request for get all address objects");
    ne_request* ret = ne_request_create(session, "PROPFIND", owner->url().encodedPath().constData());
    ne_add_request_header(ret, "Depth", "1");
    return ret;
}

ne_request* DownloadJob::Impl::createDataRequest(ne_session* session) {
    static std::string s_body = "";

    log("building request for get data for the address objects");
    ne_request* ret = ne_request_create(session, "REPORT", owner->url().encodedPath().constData());
    ne_add_request_header(ret, "Depth", "1");

    s_body =
    "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
    "<C:addressbook-multiget xmlns:D=\"DAV:\""
    "                 xmlns:C=\"urn:ietf:params:xml:ns:carddav\">"
    " <D:prop>"
    "   <D:getetag/>"
    "   <C:address-data />"
    " </D:prop>";
    for (size_t i = 0; i < hrefs.size(); ++i) {
        s_body += "<D:href>" + hrefs[i] + "</D:href>";
    }
    s_body += "</C:addressbook-multiget>";

    // s_body is static because this method doesn't do copying.
    ne_set_request_body_buffer(ret, s_body.c_str(), s_body.length());
    return ret;
}

void DownloadJob::Impl::clearResource(ResourceCardDav* customer) {
/*    for (ResourceCardDav::Iterator it = customer->begin(); customer->end() != it; it = customer->begin()) {
        customer->removeAddressee(*it);
    }*/
    customer->clear();
}

int DownloadJob::Impl::startelm_cb(void *userdata, int parent, const char */*nspace*/, const char *name, const char **/*atts*/) {
    Impl* impl = static_cast<Impl*>(userdata);
    int ret = XML_CONTINUE;
    if (0 == strcmp(name, "response")) {
        ret = XML_RESPONSE;
        impl->cur_href = "";
    }
    else if (0 == strcmp(name, "href") && XML_RESPONSE == parent) {
        ret = XML_HREF;
    }
    else if (0 == strcmp(name, "resourcetype")) {
        ret = XML_RESOURCE_TYPE;
    }
    else if (0 == strcmp(name, "collection") && XML_RESOURCE_TYPE == parent) {
        // it's a collection. Should be skipped.
        // this code depends on <href> appearing before <collection>
        // the ordering seems to be consistent for DAViCal and Zimbra
        ret = XML_COLLECTION;
        impl->cur_href = "";
    }
    else if (0 == strcmp(name, "address-data")) {
        ret = XML_ADDRESS_DATA;
    }
    else if (0 == strcmp(name, "multistatus")) {
        ret = XML_ADDRESS_MULTISTATUS;
        impl->vcards.clear();
        impl->hrefs.clear();
    }
    return ret;
}

int DownloadJob::Impl::cdata_cb(void *userdata, int state, const char *cdata, size_t len) {
    Impl* impl = static_cast<Impl*>(userdata);
    int ret = 0;
    if (XML_HREF == state) {
        impl->cur_href = std::string(cdata, len);
    }
    else if (XML_ADDRESS_DATA == state) {
        impl->vcards.append(cdata, len);
    }
    return ret;
}

int DownloadJob::Impl::endelm_cb(void *userdata, int /*state*/, const char */*nspace*/, const char *name) {
    Impl* impl = static_cast<Impl*>(userdata);
    if (0 == strcmp(name, "response") && !impl->cur_href.empty()) {
        impl->hrefs.push_back(impl->cur_href);
        log("got href: " << QString::fromStdString(impl->cur_href));
    }
    else if (0 == strcmp(name, "multistatus") && GET_OBJECTS_DATA == impl->state) {
    }
    return 0;
}

/*=========================================================================
| CONSTRUCTOR AND DESTRUCTOR
 ========================================================================*/

DownloadJob::DownloadJob(const QUrl& aUrl)
    : Job(aUrl)
    , m_i(new Impl(this))
{
}

DownloadJob::~DownloadJob() {
    delete m_i;
}


/*=========================================================================
| PUBLIC METHODS
 ========================================================================*/

/*=========================================================================
| PRIVATE METHODS
 ========================================================================*/

ne_request* DownloadJob::createRequest(ne_session* session) {
    m_i->destroyParser();
    m_i->parser = ne_xml_create();
    ne_xml_push_handler(m_i->parser,
                        Impl::startelm_cb,
                        Impl::cdata_cb,
                        Impl::endelm_cb,
                        m_i);
    ne_request* ret = 0;
    switch(m_i->state) {
    case Impl::GET_OBJECTS_LIST:
        ret = m_i->createListRequest(session);
        break;
    case Impl::GET_OBJECTS_DATA:
        ret = m_i->createDataRequest(session);
        break;
    default:
        log("illegal call");
        break;
    };
    return ret;
}

bool DownloadJob::requestAgain() {
    log("state: " << m_i->state << ", finished state: " << Impl::FINISHED);
    return Impl::FINISHED > m_i->state;
}

void DownloadJob::blockRead(const char *buf, size_t len) {
    int e = ne_xml_parse(m_i->parser, buf, len);
    // debug code below
    log("blockRead called, len " << len << ", parser return value: " << e);
    if (0 < len) {
        std::string data(buf, len);
        log("buf:\n" << QString::fromStdString(data));
    }
}

void DownloadJob::requestFinished() {
    ++m_i->state;
}

void DownloadJob::jobFinished(ResourceCardDav* customer) {
    if (m_i->vcards.isEmpty()) {
        return;
    }
    VCardConverter converter;
    Addressee::List list = converter.parseVCards(m_i->vcards);
    const int k_size = list.size();
    log("got addressee list of size " << k_size);
    if (0 == k_size) {
        // vcards are not empty, but no addressees are parsed.
        // It's strange. Possibly a corruted answer from the server.
        // Anyway we shouldn't do anything in this case.
        log("vcards are not empty, but no addressees are parsed.");
    } else {
        log("customer resource dump:");
        customer->dump();
        m_i->clearResource(customer);
        KABC::AddressBook* book = customer->addressBook();
        if (book) {
            for (int i = 0; i < list.size(); ++i) {
                Addressee& a = list[i];
                a.setResource(customer);
                book->insertAddressee(a);
            }
            customer->clearChanges();
        } else {
            log("the resource's address book is NULL!");
        }
    }
}

// EOF ========================================================================
